; simple linear block bootloader for easy testing
;
;  Copyright 2006 Corey Tabaka
;
;  Licensed under the Apache License, Version 2.0 (the "License");
;  you may not use this file except in compliance with the License.
;  You may obtain a copy of the License at
;
;      http://www.apache.org/licenses/LICENSE-2.0
;
;  Unless required by applicable law or agreed to in writing, software
;  distributed under the License is distributed on an "AS IS" BASIS,
;  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;  See the License for the specific language governing permissions and
;  limitations under the License.
;

org 0x7c00				; Start address 0000:7c00

%define	TARGET 0x200000	; destination segment address to put next sectors
%define TEMP 0x900		; temp load area
%define SEGMENTS 2		; number of segments in this bootloader

%define putc_xy(x, y, c) mov word [gs:y * 80 * 2 + x * 2], c

%define DEBUG_N(x) mov word [gs:x * 80 * 2], 0x0700 | '0' + x

%define DEBUG 0

%if 1
%define SECTORS_PER_TRACK 18
%define HEADS_PER_CYLINDER 2
%define NUM_SECTORS 0
%define DRIVE_NUM 0x00
%else
%define SECTORS_PER_TRACK 63
%define HEADS_PER_CYLINDER 16
%define NUM_SECTORS 0
%define DRIVE_NUM 0x81
%endif

struc mb_mmap_t
	mb_mmap_size:			resd 1
	mb_mmap_addr_low:		resd 1
	mb_mmap_addr_high:		resd 1
	mb_mmap_length_low:		resd 1
	mb_mmap_length_high:	resd 1
	mb_mmap_type:			resd 1
endstruc

bits 16
begin_boot:
	xor eax,eax
	mov ds,ax			; ds=0
	mov es,ax
	
	; save drive number left in dl by bios/bootloader
	mov [drive],dl
	
	; set base video mode (in case of booting from GRUB graphical mode, etc)
	mov ax,0x0003		; ah = service, al = 80x25 16 color
	int 0x10
	
	; clear the screen the hard way
	mov ax,0xb800
	mov gs,ax
	mov ax,0x0720		; ' '
	mov cx,(80 * 25)
	xor di,di
.loop:
	mov word [gs:di],ax
	add di,2
	loop .loop
	
	; display loading message
	mov ax,0x1301		; ah = service 0x13, al = 0x01 attributes in bl, move cursor
	mov bx,0x0007		; bh = 0x00 video page, bl = 0x07 grey on black
	xor dx,dx			; row = 0, col = 0
	mov bp,load_string	; es:bp = &load_string
	mov cx,31			; string len
	
	int 0x10
	
	; load additional segments
	xor ax,ax			; load sgements at es:bx = 0000:7e00
	mov es,ax
	mov bx,7e00h
	
	xor dh,dh			; head = 0
	mov dl,[drive]		; drive = drive bootsector was loaded from
	xor ch,ch			; cylinder[8:0] = 0
	mov cl,2			; sector = 2
	mov ah,2			; read sectors
	mov al,SEGMENTS-1	; sector count = number of segments after the boot sector
	
	int 13h
	
	; fetch system memory map
	xor ebx,ebx
	mov di,memory_map	; address of memory map destination buffer
.loop2:
	mov eax,0000E820h
	mov edx,534D4150h	; 'SMAP'
	mov ecx,20			; size of buffer
	int 15h
	
	jc .done
	add ecx,4
	mov [di - 4],ecx
	add [mb_mmap_length],ecx
	add di,cx
	
	cmp ebx,0
	jne .loop2
	
.done:
	cmp dword [memory_map_size],0
	je .next
	
	putc_xy(79, 0, 0700h | '!')
	mov byte [mmap_valid],1
	or dword [mb_flags],0x00000020
	
.next:
	jmp sector2

; data relevant to sector 1 (and sector 2 when loaded)
load_string:
	db "Loading [                     ]",0
done_string:
	db "Done",0
drive:
	db 0
	
mmap_valid:
	db 0

	; fill bytes from present loc to 510 with 0s
	times 510-($-$$) db 0
	dw 0x0aa55			; write aa55 in bytes 511,512 to indicate that it is a bootable sector.

; sector 2 is located just after the boot sector on the disk, making it easy to locate and load
sector2:

; read sectors into [TEMP:0000] then copy high to TARGET
.read:
	mov ax, TEMP		; segment LOC, offset 0
	mov es, ax
	
	xor dx,dx
	mov bx,(HEADS_PER_CYLINDER * SECTORS_PER_TRACK)		; bx = heads per cylinder * sectors per track
	mov ax,[lba_sector]	; current linear block address
	div bx				; ax = dx:ax / bx, dx = dx:ax % bx
	
	mov ch,al			; cylinder bits 7:0
	shl ah,6			; shift cylindar bits 9:8 into bits 7:6
	mov cl,ah			; store high cylindar bits in cl (sector)
	
	mov ax,dx
	mov bl,SECTORS_PER_TRACK
	div bl				; al = ax / SECTORS_PER_TRACK, ah = ax % SECTORS_PER_TRACK
	
	mov dh,al			; head
	
	add ah,1
	or cl,ah			; sector in bits 5:0, cylindar high in bits 7:6
	
	mov bx,0			; offset from es
	mov dl,[drive]		;DRIVE_NUM	; drive
	mov al,1 			; number of sectors
	mov ah,2 			; read sectors

	int 0x13
	
	xor ax,ax			; copy sector to extended memory
	mov es,ax
	mov si,bios_gdt		; es:si = &bios_gdt
	mov cx,512			; 512 bytes = 128 words to transfer
	mov ah,0x87			; transfer to extended memory service
	
	int 0x15
	
	;jc .error			; check for error while copying high
	
	add dword [bios_dest],0x200	; increment destination base address in the bios gdt
	cld
	inc word [lba_sector]	; increment the lba sector address
	
	; update the progress bar
	xor dx,dx
	mov ax,[lba_sector]	; get current sector lba
	dec ax				; kernel sector starts at lba=1, convert to rel offset
	mov bx,100/5		; scale kernel sector offset by 100/5
	mul bx
	mov bx,NUM_SECTORS+1; divide by number of kernel sectors to get percent done
	div bx				; ax = dx:ax / bx
	shl ax,1			; CGA mem is one char every 2 bytes
	mov bx,ax
	mov word [gs:bx + 2*9],0x07db
	
	cmp word [lba_sector],NUM_SECTORS+SEGMENTS
	
	jne .read			; loop until all sectors are loaded
	
	;DEBUG_N(0);
	
	; turn off the drive motor
	mov dx,3F2h
    mov al,0
    out dx,al
	
	; display done message
	mov ax,0x1301		; ah = service 0x13, al = 0x01 attributes in bl, move cursor
	mov bx,0x0007		; bh = 0x00 video page, bl = 0x07 grey on black
	mov dx,0x0100		; row = 0, col = 0
	mov bp,done_string	; es:bp = &load_string
	mov cx,4			; string len
	
	int 0x10
	
	; switch to protected mode
	cli					; clear or disable interrupts
	call enable_a20_fast	; enable A20 address line
	lgdt [gdtr]
	mov eax,cr0			; the lsb of cr0 is the protected mode bit
	or eax,1			; set protected mode bit
	mov cr0,eax			; mov modified word to the control register
	jmp codesel:go_pm	; far jump into protected mode

enable_a20_fast:
	push ax
	in al,92h
	or al,2				; AND ~2 to disable A20
	out 92h,al
	pop ax
	ret

bits 32
go_pm:
	mov eax,datasel
	mov ds,ax			; init data segments
	mov ss,ax
	mov es,ax
	mov fs,ax
	mov gs,ax
	
	;mov esp,10000h		; leave stack at default location from BIOS load
	
	xor ebp,ebp
	xor edi,edi
	xor esi,esi
	xor ecx,ecx
	xor edx,edx

%if DEBUG && 0	
	; peek at a memory address where the kernel should be
	mov edx,[TARGET + 0x40090]
	mov edi, 0*80*2 + 10*2
	call print_hex32
	
	;jmp $
	mov word [0xb8000 + 0 * 80 * 2], 0x0700 | 'A' ; display white A in protected mode
%endif
	
	mov eax,0x2BADB002
	mov ebx,mb_info
	
.done:	
	jmp codesel:TARGET			; transfer control to loaded kernel
	
	pause
.spin:
	jmp .spin			; loop

; data relevant to sector 2
lba_sector:
	dw SEGMENTS			; current sector to read (lower lba word)
	dw 0				; upper lba word (unused)
	dd 0
	
gdtr:
	dw gdt_end - gdt -1	; descriptor limit
	dd gdt
	;dw 0

; global descriptor table
; one null and three flat linear segments	
gdt:
	dd 0
	dd 0
	
codesel equ $-gdt
code_desc:
	dw 0xffff			; limit 15..0
	dw 0x0000			; base 23..0
	db 0x00
	db 10011010b		; P(1) DPL(00) S(1) 1 C(0) R(1) A(0)
	db 11001111b		; G(1) D(1) 0 0 Limit[19..16]
	db 0x0				; base 31..24
	
datasel equ $-gdt
data_desc:
	dw 0xffff
	dw 0x0000
	db 0x00
	db 10010010b		; P(1) DPL(00) S(1) 0 E(0) W(1) A(0)
	db 11001111b		; G(1) B(1) 0 0 Limit[19..16]
	db 0x0
	
linearsel equ $-gdt
linear_desc:
	dw 0xffff
	dw 0x0000
	db 0x00
	db 10010010b		; P(1) DPL(00) S(1) 0 E(0) W(1) A(0)
	db 11001111b		; G(1) B(1) 0 0 Limit[19..16]
	db 0x0

videosel equ $-gdt
video_desc:
	dw 0xffff
	dw 0x8000
	db 0x0b
	db 10010010b		; P(1) DPL(00) S(1) 0 E(0) W(1) A(0)
	db 11001111b		; G(1) B(1) 0 0 Limit[19..16]
	db 0x0
	
gdt_end:

bios_gdt:
	dd 0
	dd 0
	dd 0
	dd 0
	
	dw 0xffff
	dw TEMP * 16
	db 0x00
	db 10010011b		; P(1) DPL(00) S(1) 0 E(0) W(1) A(0)
	db 11001111b		; G(1) B(1) 0 0 Limit[19..16]
	db 0x0

	dw 0xffff
bios_dest:
	dw 0x0000
	db TARGET >> 16
	db 10010011b		; P(1) DPL(00) S(1) 0 E(0) W(1) A(0)
	db 11001111b		; G(1) B(1) 0 0 Limit[19..16]
	db 0x0
	
	dd 0
	dd 0
	dd 0
	dd 0

; multiboot information structure
mb_info:
mb_flags:
	dd 0
mb_mem_lower:
	dd 0
mb_mem_upper:
	dd 0
mb_boot_device:
	dd 0
mb_cmd_line:
	dd 0
mb_mods_count:
	dd 0
mb_mode_addr:
	dd 0
mb_syms:
	dd 0
	dd 0
	dd 0
	dd 0
mb_mmap_length:
	dd 0
mb_mmap_addr:
	dd memory_map
mb_drives_length:
	dd 0
mb_drives_addr:
	dd 0
mb_config_table:
	dd 0
mb_boot_loader_name:
	dd 0
mb_apm_table:
	dd 0
mb_vbe_control_info:
	dd 0
	dd 0
	dd 0
	dd 0
	dd 0
	dd 0

memory_map_size:
	dd 0
; if supported, the BIOS memory map will be constructed starting at this address
memory_map:
	
	; fill bytes from present loc to end with 0s
	times (512*SEGMENTS)-($-$$) db 0